local Class = require('lua.lang.Class')
local Token = require('lua.vm.compiler.Token')
local DynamicValue = require('lua.vm.compiler.DynamicValue')
local LuaString = require('lua.vm.LuaString')
local Lua = require('lua.vm.Lua')
local LuaError = require('lua.vm.LuaError')
local Constants = require('lua.vm.compiler.Constants')
local LuaNumber = require('lua.vm.LuaNumber')
local BlockControl = require('lua.vm.compiler.BlockControl')
local IntPointer = require('lua.vm.compiler.IntPointer')
local LocalVariable = require('lua.vm.LocalVariable')
local VariableDescriptor = require('lua.vm.compiler.VariableDescriptor')
local ExpressionDescriptor = require('lua.vm.compiler.ExpressionDescriptor')
local LabelDescriptor = require('lua.vm.compiler.LabelDescriptor')
local Prototype = require('lua.vm.Prototype')
local TableConstructorControl = require('lua.vm.compiler.TableConstructorControl')
local InstructionPointer = require('lua.vm.compiler.InstructionPointer')
local Priority = require('lua.vm.compiler.Priority')
local LeftHandAssignment = require('lua.vm.compiler.LeftHandAssignment')
local FunctionState = require('lua.vm.compiler.FunctionState')

local B_ZERO = ('0'):byte()
local B_NINE = ('9'):byte()
local B_a = ('a'):byte()
local B_b = ('b'):byte()
local B_n = ('n'):byte()
local B_r = ('r'):byte()
local B_t = ('t'):byte()
local B_v = ('v'):byte()
local B_z = ('z'):byte()
local B_A = ('A'):byte()
local B_Z = ('Z'):byte()
local B_f = ('f'):byte()
local B_F = ('F'):byte()
local B_SPACE = (' '):byte()
local B_CR = ('\r'):byte()
local B_LF = ('\n'):byte()
local B_COLON = (':'):byte()
local B_UNDERSCORE = ('_'):byte()
local B_EQUALS = ('='):byte()
local B_MINUS = ('-'):byte()
local B_DOT = ('.'):byte()
local B_HASH = ('#'):byte()
local B_x = ('x'):byte()
local B_X = ('X'):byte()
local B_p = ('p'):byte()
local B_P = ('P'):byte()
local B_LBRACKET = ('['):byte()
local B_RBRACKET = (']'):byte()
local B_LPAREN = ('('):byte()
local B_RPAREN = (')'):byte()
local B_BACKSLASH = ('\\'):byte()
local B_SLASH = ('/'):byte()
local B_LESS_THAN = ('<'):byte()
local B_GREATER_THAN = ('>'):byte()
local B_TILDE = ('~'):byte()
local B_LCURLY = ('{'):byte()
local B_RCURLY = ('}'):byte()
local B_COMMA = (','):byte()
local B_SEMICOLON = (';'):byte()
local B_PLUS = ('+'):byte()
local B_STAR = ('*'):byte()
local B_PERCENT = ('%'):byte()
local B_CARET = ('^'):byte()
local B_AMPERSAND = ('&'):byte()
local B_PIPE = ('|'):byte()
local B_BELL = ('\a'):byte()
local B_BACKSPACE = ('\b'):byte()
local B_FORM_FEED = ('\f'):byte()
local B_VERTICAL_TAB = ('\v'):byte()
local B_TAB = ('\t'):byte()

--- @class LexState : Constants
--- @field ec number[]
--- @field z InputStream
--- @field buff number[]
--- @field nCalls number
--- @field current number
--- @field linenumber number
--- @field lastLine number
--- @field t Token
--- @field lookahead Token
--- @field fs FunctionState?
--- @field nBuff number
--- @field dyd DynamicValue
--- @field source LuaString
--- @field envName LuaString
--- @field decPoint number
local LexState = Class {
	__extends = Constants
}

LexState.RESERVED_LOCAL_VAR_FOR_CONTROL = '(for control)'
LexState.RESERVED_LOCAL_VAR_FOR_STATE = '(for state)'
LexState.RESERVED_LOCAL_VAR_FOR_GENERATOR = '(for generator)'
LexState.RESERVED_LOCAL_VAR_FOR_STEP = '(for step)'
LexState.RESERVED_LOCAL_VAR_FOR_LIMIT = '(for limit)'
LexState.RESERVED_LOCAL_VAR_FOR_INDEX = '(for index)'

--- @type string[]
LexState.RESERVED_LOCAL_VAR_KEYWORDS = {
	LexState.RESERVED_LOCAL_VAR_FOR_CONTROL,
	LexState.RESERVED_LOCAL_VAR_FOR_GENERATOR,
	LexState.RESERVED_LOCAL_VAR_FOR_INDEX,
	LexState.RESERVED_LOCAL_VAR_FOR_LIMIT,
	LexState.RESERVED_LOCAL_VAR_FOR_STATE,
	LexState.RESERVED_LOCAL_VAR_FOR_STEP
}

--- @type table<string, boolean>
LexState.RESERVED_LOCAL_VAR_KEYWORDS_TABLE = {}

for i = 1, #LexState.RESERVED_LOCAL_VAR_KEYWORDS do
	LexState.RESERVED_LOCAL_VAR_KEYWORDS_TABLE[LexState.RESERVED_LOCAL_VAR_KEYWORDS[i]] = true
end

--- @type string[]
LexState.luaXTokens = {
	'and', 'break', 'do', 'else', 'elseif',
	'end', 'false', 'for', 'function', 'goto',
	'if', 'in', 'local', 'nil', 'not',
	'or', 'repeat', 'return', 'then', 'true',
	'until', 'while', '..', '...', '==',
	'>=', '<=', '~=', '::', '<eos>', '<number>',
	'<name>', '<string>', '//', '<<', '>>',
	'<eof>'
}

LexState.TK_AND = 257
LexState.TK_BREAK = 258
LexState.TK_DO = 259
LexState.TK_ELSE = 260
LexState.TK_ELSEIF = 261
LexState.TK_END = 262
LexState.TK_FALSE = 263
LexState.TK_FOR = 264
LexState.TK_FUNCTION = 265
LexState.TK_GOTO = 266
LexState.TK_IF = 267
LexState.TK_IN = 268
LexState.TK_LOCAL = 269
LexState.TK_NIL = 270
LexState.TK_NOT = 271
LexState.TK_OR = 272
LexState.TK_REPEAT = 273
LexState.TK_RETURN = 274
LexState.TK_THEN = 275
LexState.TK_TRUE = 276
LexState.TK_UNTIL = 277
LexState.TK_WHILE = 278
LexState.TK_CONCAT = 279
LexState.TK_DOTS = 280
LexState.TK_EQ = 281
LexState.TK_GE = 282
LexState.TK_LE = 283
LexState.TK_NE = 284
LexState.TK_DBCOLON = 285
LexState.TK_EOS = 286
LexState.TK_NUMBER = 287
LexState.TK_NAME = 288
LexState.TK_STRING = 289
LexState.TK_IDIV = 290
LexState.TK_SHL = 291
LexState.TK_SHR = 292

LexState.FIRST_RESERVED = LexState.TK_AND
LexState.NUM_RESERVED = LexState.TK_WHILE + 1 - LexState.FIRST_RESERVED

--- @type table<string, number>
LexState.RESERVED = {}

for i = 1, LexState.NUM_RESERVED do
	LexState.RESERVED[LexState.luaXTokens[i]] = LexState.FIRST_RESERVED + i - 1
end

--- @type Priority[]
LexState.priority = {
	Priority:new(60, 60),
	Priority:new(60, 60),
	Priority:new(70, 70),
	Priority:new(70, 70),
	Priority:new(70, 70),
	Priority:new(100, 90),
	Priority:new(50, 40),
	Priority:new(30, 30),
	Priority:new(30, 30),
	Priority:new(30, 30),
	Priority:new(30, 30),
	Priority:new(30, 30),
	Priority:new(30, 30),
	Priority:new(20, 20),
	Priority:new(10, 10),
	Priority:new(70, 70),
	Priority:new(36, 36),
	Priority:new(32, 32),
	Priority:new(34, 34),
	Priority:new(38, 38),
	Priority:new(38, 38)
}

LexState.UNARY_PRIORITY = 80

--- @param s string
--- @return string
function LexState:LUA_QS(s)
	return '\39' .. s .. '\39'
end

--- @param o any
--- @return string
function LexState:LUA_QL(o)
	return self:LUA_QS(tostring(o))
end

--- @param varName string
--- @return boolean
function LexState:isReservedKeyword(varName)
	return not not self.RESERVED_LOCAL_VAR_KEYWORDS_TABLE[varName]
end

--- @constructor
--- @param stream InputStream
--- @return LexState
function LexState:new(stream)
	--- @type LexState
	local o = {}
	o.z = stream
	o.buff = {}
	o.nCalls = 0
	o.ec = {}
	o.current = 0
	o.linenumber = 0
	o.lastLine = 0
	o.t = Token:new()
	o.lookahead = Token:new()
	o.nBuff = 0
	o.dyd = DynamicValue:new()
	o.decPoint = 0
	return setmetatable(o, LexState)
end

--- @param c number
--- @return boolean
function LexState:isAlphaNum(c)
	return (c >= B_A and c <= B_Z) or (c >= B_a and c <= B_z) or (c >= B_ZERO and c <= B_NINE) or c == B_UNDERSCORE
end

--- @param c number
--- @return boolean
function LexState:isAlpha(c)
	return (c >= B_A and c <= B_Z) or (c >= B_a and c <= B_z)
end

--- @param c number
--- @return boolean
function LexState:isExtra(c)
	return c > 127
end

--- @param c number
--- @return boolean
function LexState:isDigit(c)
	return c >= B_ZERO and c <= B_NINE
end

--- @param c number
--- @return boolean
function LexState:isXDigit(c)
	return (c >= B_ZERO and c <= B_NINE) or (c >= B_A and c <= B_F) or (c >= B_a and c <= B_f)
end

--- @param c number
--- @return boolean
function LexState:isSpace(c)
	return c <= B_SPACE
end

--- @return void
function LexState:nextChar()
	self.current = self.z:read()
end

--- @return boolean
function LexState:currIsNewline()
	return self.current == B_LF or self.current == B_CR
end

--- @return void
function LexState:saveAndNext()
	self:save(self.current)
	self:nextChar()
end

--- @param c number
--- @return void
function LexState:save(c)
	local buff = self.buff
	if buff == nil then
		buff = {}
		self.buff = buff
	end
	local nBuff = self.nBuff + 1
	self.nBuff = nBuff
	buff[nBuff] = c
end

--- @param token number
--- @return string
function LexState:token2Str(token)
	if token < self.FIRST_RESERVED then
		if self:isControl(token) then
			return 'char(' .. tostring(token) .. ')'
		else
			return tostring(token)
		end
	end
	return self.luaXTokens[token - self.FIRST_RESERVED + 1]
end

--- @param c number
--- @return boolean
function LexState:isControl(c)
	return c < B_SPACE
end

--- @param token number
--- @return string
function LexState:txtToken(token)
	if token == self.TK_NAME or token == self.TK_STRING or token == self.TK_NUMBER then
		return self:packBuff()
	end
	return self:token2Str(token)
end

--- @param msg string
--- @param token number
--- @return void
function LexState:lexError(msg, token)
	local cid = Lua:chunkId(self.source:toLString())
	LuaError:new(cid .. ':' .. tostring(self.linenumber) .. ':' .. msg .. ' near ' .. self:txtToken(token)):throw()
end

--- @param msg string
--- @return void
function LexState:syntaxError(msg)
	self:lexError(msg, self.t.token)
end

--- @param from number?
--- @param to number?
--- @return string
function LexState:packBuff(from, to)
	from = from or 1
	to = to or self.nBuff
	return string.char(table.unpack(self.buff, from, to))
end

--- @param from number?
--- @param to number?
--- @return LuaString
function LexState:packBuffToLuaString(from, to)
	return LuaString:valueOf(self:packBuff(from, to))
end

--- @return void
function LexState:inclineNumber()
	local old = self.current
	self:assert(self:currIsNewline())
	self:nextChar()
	if self:currIsNewline() and self.current ~= old then
		self:nextChar()
	end
	local linenumber = self.linenumber + 1
	self.linenumber = linenumber
	if linenumber >= self.MAX_INT then
		self:syntaxError('chunk has too many lines')
	end
end

--- @param firstByte number
--- @param z InputStream
--- @param source LuaString
--- @return void
function LexState:setInput(firstByte, z, source)
	self.decPoint = B_DOT
	self.lookahead.token = self.TK_EOS
	self.z = z
	self.fs = nil
	self.linenumber = 1
	self.lastLine = 1
	self.source = source
	self.envName = LuaString:valueOf('_ENV')
	self.nBuff = 0
	self.current = firstByte
	self:skipShebang()
end

--- @return void
function LexState:skipShebang()
	if self.current == B_HASH then
		while not self:currIsNewline() and current ~= self.EOZ do
			self:nextChar()
		end
	end
end

--- @param set string
--- @return boolean
function LexState:consume(set)
	local bytes = set:toByteArray()
	local current = self.current
	for i = 1, #bytes do
		if bytes[i] == current then
			self:saveAndNext()
			return true
		end
	end
	return false
end

--- @param str string
--- @return LuaValue
function LexState:hexStr2Number(str)
	local c = str:toCharArray()
	local cLength = #c
	local s = 0
	while s < cLength and self:isSpace(c[s + 1]) do
		s = s + 1
	end
	local sgn = 1.0
	if s < cLength and c[s + 1] == B_MINUS then
		sgn = -1.0
		s = s + 1
	end
	if s + 2 >= cLength then
		return LuaNumber.ZERO
	end
	s = s + 1
	if c[s] ~= B_ZERO then
		return LuaNumber.ZERO
	end
	if c[s + 1] ~= B_x and c[s + 1] ~= B_X then
		return LuaNumber.ZERO
	end
	s = s + 1
	local m = 0
	local e = 0
	while s < cLength and self:isXDigit(c[s + 1]) do
		s = s + 1
		m = m * 16 + self:hexValue(c[s])
	end
	if s < cLength and c[s + 1] == B_DOT then
		s = s + 1
		while s < cLength and self:isXDigit(c[s + 1]) do
			s = s + 1
			m = m * 16 + self:hexValue(c[s])
			e = e - 4
		end
	end
	if s < cLength and (c[s + 1] == B_p or c[s + 1] == B_P) then
		s = s + 1
		local exp1 = 0
		local neg1 = false
		if s < cLength and c[s + 1] == B_MINUS then
			neg1 = true
			s = s + 1
		end
		while s < cLength and self:isDigit(c[s + 1]) do
			s = s + 1
			exp1 = exp1 * 10 + c[s] - B_ZERO
		end
		if neg1 then
			exp1 = -exp1
		end
		e = e + exp1
	end
	return LuaNumber:new(sgn * m * 2.0 ^ e)
end

--- @param str string
---  @param semInfo SemanticInfo
--- @return void
function LexState:str2d(str, semInfo)
	if str:find('[nN]') then
		semInfo.numericValue = LuaNumber.ZERO
	elseif str:find('[xX]') then
		semInfo.numericValue = self:hexStr2Number(str)
	else
		semInfo.numericValue = LuaNumber:new(tonumber(str))
	end
end

--- @param semInfo SemanticInfo
--- @return void
function LexState:readNumeral(semInfo)
	local expo = 'Ee'
	local first = self.current
	self:assert(self:isDigit(self.current))
	self:saveAndNext()
	if first == B_ZERO and self:consume('Xx') then
		expo = 'Pp'
	end
	while true do
		if self:consume(expo) then
			self:consume('+-')
		end
		if self:isXDigit(self.current) or self.current == B_DOT then
			self:saveAndNext()
		else
			break
		end
	end
	self:str2d(self:packBuff(), semInfo)
end

--- @return number
function LexState:skipSep()
	local count = 0
	local s = self.current
	self:assert(s == B_LBRACKET or s == B_RBRACKET)
	self:saveAndNext()
	while self.current == B_EQUALS do
		self:saveAndNext()
		count = count + 1
	end
	return self.current == s and count or (-count - 1)
end

--- @param semInfo SemanticInfo
--- @param sep number
--- @return void
function LexState:readLongString(semInfo, sep)
	local cont = 0
	self:saveAndNext()
	if self:currIsNewline() then
		self:inclineNumber()
	end
	local endLoop = false
	while not endLoop do
		local curr = self.current
		if curr == self.EOZ then
			self:lexError(semInfo ~= nil and 'unfinished long string' or
					'unfinished long comment', self.TK_EOS)
		elseif curr == B_LBRACKET then
			if self:skipSep() == sep then
				self:saveAndNext()
				cont = cont + 1
				if self.LUA_COMPAT_LSTR == 1 and sep == 0 then
					self:lexError('nesting of [[...]] is deprecated', '[')
				end
			end
		elseif curr == B_RBRACKET then
			if self:skipSep() == sep then
				self:saveAndNext()
				if self.LUA_COMPAT_LSTR == 2 then
					cont = cont - 1
					if sep == 0 and cont >= 0 then
						goto continue
					end
				end
				endLoop = true
			end
			:: continue ::
		elseif curr == B_CR or curr == B_LF then
			self:save(B_LF)
			self:inclineNumber()
			if semInfo == nil then
				self.nBuff = 0
			end
		else
			if semInfo ~= nil then
				self:saveAndNext()
			else
				self:nextChar()
			end
		end
	end
	if semInfo ~= nil then
		semInfo.stringValue = LuaString:valueOf(self:packBuff(3 + sep, self.nBuff - 2 - sep))
	end
end

--- @param c number
--- @return number
function LexState:hexValue(c)
	if c <= B_NINE then
		return c - B_ZERO
	elseif c <= B_F then
		return c - B_A + 10
	else
		return c - B_a + 10
	end
end

--- @return number
function LexState:readHexEscape()
	self:nextChar()
	local c1 = self.current
	self:nextChar()
	local c2 = self.current
	if not self:isXDigit(c1) or not self:isXDigit(c2) then
		self:lexError('hexadecimal digit expected \39x' .. string.char(c1) .. string.char(c2), self.TK_STRING)
	end
	return (self:hexValue(c1) << 4) + self:hexValue(c2)
end

--- @param n number
--- @param msg string
--- @return void
function LexState:escError(n, msg)
	self:save(B_BACKSLASH)
	local i = 1
	while i <= n and self.ec[i] ~= -1 do
		self:save(self.ec[i])
		i = i + 1
	end
	self:lexError(msg, self.TK_STRING)
end

--- @param del number
--- @param semInfo SemanticInfo
--- @return void
function LexState:readString(del, semInfo)
	self:saveAndNext()
	while self.current ~= del do
		local curr = self.current
		if curr == self.EOZ then
			self:lexError('unfinished string', self.TK_EOS)
		elseif curr == B_CR or curr == B_LF then
			self:lexError('unfinished string', self.TK_STRING)
		elseif curr == B_BACKSLASH then
			--- @type number
			local c
			self:nextChar()
			local cur = self.current
			if cur == B_a then
				c = B_BELL
			elseif cur == B_b then
				c = B_BACKSPACE
			elseif cur == B_f then
				c = B_FORM_FEED
			elseif cur == B_n then
				c = B_LF
			elseif cur == B_r then
				c = B_CR
			elseif cur == B_t then
				c = B_TAB
			elseif cur == B_v then
				c = B_VERTICAL_TAB
			elseif cur == B_x then
				c = self:readHexEscape()
			elseif cur == B_CR or cur == B_LF then
				self:save(B_LF)
				self:inclineNumber()
				goto continue
			elseif cur == self.EOZ then
				goto continue
			elseif cur == B_z then
				self:nextChar()
				while self:isSpace(self.current) do
					if self:currIsNewline() then
						self:inclineNumber()
					else
						self:nextChar()
					end
				end
				goto continue
			else
				if not self:isDigit(cur) then
					self:saveAndNext()
				else
					local i = 0
					c = 0
					repeat
						c = 10 * c + (self.current - B_ZERO)
						i = i + 1
					until not (i < 3 and self:isDigit(self.current))
					if c > self.UCHAR_MAX then
						self:lexError('escape sequence too large', self.TK_STRING)
					end
					self:save(c)
				end
				goto continue
			end
			self:save(c)
			self:nextChar()
			goto continue
		else
			self:saveAndNext()
		end
		:: continue ::
	end
	self:saveAndNext()
	semInfo.stringValue = self:packBuffToLuaString(2, self.nBuff - 1)
end

--- @param semInfo SemanticInfo
--- @return number
function LexState:lLex(semInfo)
	self.nBuff = 0
	while true do
		local curr = self.current
		if curr == B_CR or curr == B_LF then
			self:inclineNumber()
			goto continue
		elseif curr == B_MINUS then
			self:nextChar()
			if self.current ~= B_MINUS then
				return B_MINUS
			end
			self:nextChar()
			if self.current == B_LBRACKET then
				local sep = self:skipSep()
				self.nBuff = 0
				if sep >= 0 then
					self:readLongString(nil, sep)
					self.nBuff = 0
					goto continue
				end
			end
			while not self:currIsNewline() and self.current ~= self.EOZ do
				self:nextChar()
			end
			goto continue
		elseif curr == B_LBRACKET then
			local sep = self:skipSep()
			if sep >= 0 then
				self:readLongString(semInfo, sep)
				return self.TK_STRING
			elseif sep == -1 then
				return B_LBRACKET
			else
				self:lexError('invalid long string delimiter', self.TK_STRING)
			end
		elseif curr == B_EQUALS then
			self:nextChar()
			if self.current ~= B_EQUALS then
				return B_EQUALS
			else
				self:nextChar()
				return self.TK_EQ
			end
		elseif curr == B_LESS_THAN then
			self:nextChar()
			if self.current == B_EQUALS then
				self:nextChar()
				return self.TK_LE
			elseif self.current == B_LESS_THAN then
				self:nextChar()
				return self.TK_SHL
			end
			return B_LESS_THAN
		elseif curr == B_GREATER_THAN then
			self:nextChar()
			if self.current == B_EQUALS then
				self:nextChar()
				return self.TK_GE
			elseif self.current == B_GREATER_THAN then
				self:nextChar()
				return self.TK_SHR
			end
			return B_GREATER_THAN
		elseif curr == B_TILDE then
			self:nextChar()
			if self.current ~= B_EQUALS then
				return B_TILDE
			else
				self:nextChar()
				return self.TK_NE
			end
		elseif curr == B_COLON then
			self:nextChar()
			if self.current ~= B_COLON then
				return B_COLON
			else
				self:nextChar()
				return self.TK_DBCOLON
			end
		elseif curr == 34 or curr == 39 then
			self:readString(curr, semInfo)
			return self.TK_STRING
		elseif curr == B_DOT then
			self:saveAndNext()
			if self:consume('.') then
				if self:consume('.') then
					return self.TK_DOTS
				else
					return self.TK_CONCAT
				end
			elseif not self:isDigit(self.current) then
				return B_DOT
			else
				self:readNumeral(semInfo)
				return self.TK_NUMBER
			end
		elseif curr >= B_ZERO and curr <= B_NINE then
			self:readNumeral(semInfo)
			return self.TK_NUMBER
		elseif curr == self.EOZ then
			return self.TK_EOS
		elseif curr == B_SLASH then
			self:nextChar()
			if self.current == B_SLASH then
				self:nextChar()
				return self.TK_IDIV
			end
			return B_SLASH
		else
			if self:isSpace(curr) then
				self:assert(not self:currIsNewline())
				self:nextChar()
				goto continue
			elseif self:isDigit(curr) then
				self:readNumeral(semInfo)
				return self.TK_NUMBER
			elseif self:isAlpha(curr) or self:isExtra(curr) or curr == B_UNDERSCORE then
				--- @type string
				local ts
				repeat
					self:saveAndNext()
				until not (self:isAlphaNum(self.current) or self:isExtra(self.current) or
						self.current == B_UNDERSCORE)
				ts = self:packBuff()
				local n = self.RESERVED[ts]
				if n then
					return n
				else
					semInfo.stringValue = LuaString:valueOf(ts)
					return self.TK_NAME
				end
			else
				local c = self.current
				self:nextChar()
				return c
			end
		end
		:: continue ::
	end
end

--- @return void
function LexState:next()
	self.lastLine = self.linenumber
	if self.lookahead.token ~= self.TK_EOS then
		self.t:set(self.lookahead)
		self.lookahead.token = self.TK_EOS
	else
		self.t.token = self:lLex(self.t.semanticInfo)
	end
end

--- @return void
function LexState:lookAhead()
	self:assert(self.lookahead.token == self.TK_EOS)
	self.lookahead.token = self:lLex(self.lookahead.semanticInfo)
end

--- @param k boolean
--- @return boolean
function LexState:hasMultipleRet(k)
	return k == self.VCALL or k == self.VVARARG
end

--- @param msg string
--- @return void
function LexState:semError(msg)
	self.t.token = 0
	self:syntaxError(msg)
end

--- @param token number
--- @return void
function LexState:errorExpected(token)
	self:syntaxError(self:LUA_QS(self:token2Str(token)) .. ' expected')
end

--- @param c number
--- @return boolean
function LexState:testNext(c)
	if self.t.token == c then
		self:next()
		return true
	else
		return false
	end
end

--- @param c number
--- @return void
function LexState:check(c)
	if self.t.token ~= c then
		self:errorExpected(c)
	end
end

--- @param c number
--- @return void
function LexState:checkNext(c)
	self:check(c)
	self:next()
end

--- @param c boolean
--- @param msg string
--- @return void
function LexState:checkCondition(c, msg)
	if not c then
		self:syntaxError(msg)
	end
end

--- @param what number
--- @param who number
--- @param where number
--- @return void
function LexState:checkMatch(what, who, where)
	if not self:testNext(what) then
		if where == self.linenumber then
			self:errorExpected(what)
		else
			self:syntaxError(self:LUA_QS(self:token2Str(what)) .. ' expected (to close ' ..
					self:LUA_QS(self:token2Str(who)) .. ' at line ' .. where .. ')')
		end
	end
end

--- @return LuaString
function LexState:strCheckName()
	self:check(self.TK_NAME)
	local ts = self.t.semanticInfo.stringValue
	self:next()
	return ts
end

--- @param e ExpressionDescriptor
--- @param s LuaString
--- @return void
function LexState:codeString(e, s)
	e:initialize(self.VK, self.fs:stringK(s))
end

--- @param e ExpressionDescriptor
--- @return void
function LexState:checkName(e)
	self:codeString(e, self:strCheckName())
end

--- @param varName LuaString
--- @return number
function LexState:registerLocalVar(varName)
	local fs = self.fs
	local f = fs.f
	local locvars = f.locVars
	if locvars == nil then
		locvars = {}
		f.locVars = locvars
	end
	local nLocVars = fs.nLocVars + 1
	fs.nLocVars = nLocVars
	locvars[nLocVars] = LocalVariable:new(varName, 0, 0)
	return nLocVars - 1
end

--- @param name LuaString
--- @return void
function LexState:newLocalVar(name)
	local reg = self:registerLocalVar(name)
	local dyd = self.dyd
	local nActVar = dyd.activeVariableCount + 1
	self.fs:checkLimit(nActVar, self.LUAI_MAXVARS, 'local variables')
	dyd.activeVariableCount = nActVar
	local actVar = dyd.activeVariables
	if actVar == nil then
		actVar = {}
		dyd.activeVariables = actVar
	end
	actVar[nActVar] = VariableDescriptor:new(reg)
end

--- @param v string
--- @return void
function LexState:newLocalVarLiteral(v)
	self:newLocalVar(LuaString:valueOf(v))
end

--- @param nVars number
--- @return void
function LexState:adjustLocalVars(nVars)
	local fs = self.fs
	fs.nActVar = fs.nActVar + nVars
	for i = nVars, 1, -1 do
		fs:getLocVar(fs.nActVar - i).startPc = fs.pc
	end
end

--- @param fs FunctionState
--- @param n LuaString
--- @param var ExpressionDescriptor
--- @param base number
--- @return number
function LexState:singleVarAux(fs, n, var, base)
	if fs == nil then
		return self.VVOID
	end
	local v = fs:searchVar(n)
	if v >= 0 then
		var:initialize(self.VLOCAL, v)
		if base == 0 then
			fs:markUpvalue(v)
		end
		return self.VLOCAL
	else
		local idx = fs:searchUpvalue(n)
		if idx < 0 then
			if self:singleVarAux(fs.prev, n, var, 0) == self.VVOID then
				return self.VVOID
			end
			idx = fs:newUpvalue(n, var)
		end
		var:initialize(self.VUPVAL, idx)
		return self.VUPVAL
	end
end

--- @param var ExpressionDescriptor
--- @return void
function LexState:singleVar(var)
	local varName = self:strCheckName()
	local fs = self.fs
	if self:singleVarAux(fs, varName, var, 1) ~= self.VVOID then
		return
	end
	local key = ExpressionDescriptor:new()
	self:singleVarAux(fs, self.envName, var, 1)
	self:assert(var.expressionType == self.VLOCAL or var.expressionType == self.VUPVAL)
	self:codeString(key, varName)
	fs:indexed(var, key)
end

--- @param nVars number
--- @param nExp number
--- @param e ExpressionDescriptor
--- @return void
function LexState:adjustAssign(nVars, nExp, e)
	local fs = self.fs
	local extra = nVars - nExp
	if self:hasMultipleRet(e.expressionType) then
		extra = extra + 1
		if extra < 0 then
			extra = 0
		end
		fs:setReturns(e, extra)
		if extra > 1 then
			fs:reserveRegs(extra - 1)
		end
	else
		if e.expressionType ~= self.VVOID then
			fs:exp2NextReg(e)
		end
		if extra > 0 then
			local reg = fs.freeReg
			fs:reserveRegs(extra)
			fs:processNil(reg, extra)
		end
	end
end

--- @return void
function LexState:enterLevel()
	local nCalls = self.nCalls + 1
	self.nCalls = nCalls
	if nCalls > self.LUAI_MAXCCALLS then
		self:lexError('chunk has too many syntax levels', 0)
	end
end

--- @return void
function LexState:leaveLevel()
	self.nCalls = self.nCalls - 1
end

--- @param g number
--- @param label LabelDescriptor
--- @return void
function LexState:closeGoto(g, label)
	local fs = self.fs
	local gl = self.dyd.gotoLabels
	local gt = gl[g + 1]
	self:assert(gt.identifier:eq_b(label.identifier))
	if gt.activeVariableCount < label.activeVariableCount then
		local vName = fs:getLocVar(gt.activeVariableCount).varName
		local msg = '<goto ' .. gt.identifier:toLString() .. '> at line ' .. gt.sourceLine ..
				' jumps into the scope of local \39' .. vName:toLString() .. '\39'
		self:semError(msg)
	end
	fs:patchList(gt.pc, label.pc)
	local nGt = self.dyd.gotoLabelCount
	for i = g, nGt - 2 do
		gl[i + 1] = gl[i + 2]
	end
	gl[nGt] = nil
	self.dyd.gotoLabelCount = nGt - 1
end

--- @param g number
--- @return boolean
function LexState:findLabel(g)
	local fs = self.fs
	local bl = fs.bl
	local dyd = self.dyd
	local gt = dyd.gotoLabels[g + 1]
	for i = bl.firstLabel, dyd.labelCount - 1 do
		local lb = dyd.labels[i + 1]
		if lb.identifier:eq_b(gt.identifier) then
			if gt.activeVariableCount > lb.activeVariableCount and (bl.upvalue or dyd.labelCount > bl.firstLabel) then
				fs:patchCloseUpvalues(gt.pc, lb.activeVariableCount)
			end
			self:closeGoto(g, lb)
			return true
		end
	end
	return false
end

--- @param l LabelDescriptor[]
--- @param index number
--- @param name LuaString
--- @param line number
--- @param pc number
--- @return number
function LexState:newLabelEntry(l, index, name, line, pc)
	l[index + 1] = LabelDescriptor:new(name, pc, line, self.fs.nActVar)
	return index
end

--- @param lb LabelDescriptor
--- @return void
function LexState:findGotos(lb)
	local gl = self.dyd.gotoLabels
	local i = self.fs.bl.firstGoto
	while i < self.dyd.gotoLabelCount do
		if gl[i + 1].identifier:eq_b(lb.identifier) then
			self:closeGoto(i, lb)
		else
			i = i + 1
		end
	end
end

--- @return void
function LexState:breakLabel()
	local n = LuaString:valueOf('break')
	local dyd = self.dyd
	--- @type LabelDescriptor[]
	local label = dyd.labels or {}
	dyd.labels = label
	local l = self:newLabelEntry(label, dyd.labelCount, n, 0, self.fs.pc)
	dyd.labelCount = dyd.labelCount + 1
	self:findGotos(label[l + 1])
end

--- @param gt LabelDescriptor
--- @return void
function LexState:undefGoto(gt)
	local msg = self:isReservedKeyword(gt.identifier:toLString()) and
			'<' .. gt.identifier:toLString() .. '> at line ' .. gt.sourceLine ..
					' not inside a loop' or 'no visible label \39' ..
			gt.identifier:toLString() .. '\39 for <goto> at line ' .. gt.sourceLine
	self:semError(msg)
end

--- @return Prototype
function LexState:addPrototype()
	local fs = self.fs
	local f = fs.f
	local clp = Prototype:new()
	local np = fs.np + 1
	fs.np = np
	local p = f.p
	if p == nil then
		p = {}
		f.p = p
	end
	p[np] = clp
	return clp
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:codeClosure(v)
	local fs = self.fs.prev
	v:initialize(self.VRELOCABLE, fs:codeABx(self.OP_CLOSURE, 0, fs.np - 1))
	fs:exp2NextReg(v)
end

--- @param fs FunctionState
--- @param bl BlockControl
--- @return void
function LexState:openFunc(fs, bl)
	fs.prev = self.fs
	fs.ls = self
	self.fs = fs
	fs.pc = 0
	fs.lastTarget = -1
	fs.jpc = IntPointer:new(self.NO_JUMP)
	fs.freeReg = 0
	fs.nk = 0
	fs.np = 0
	fs.nUps = 0
	fs.nLocVars = 0
	fs.nActVar = 0
	fs.firstLocal = self.dyd.activeVariableCount
	fs.bl = nil
	fs.f.source = self.source
	fs.f.maxStackSize = 2
	fs:enterBlock(bl, false)
end

--- @return void
function LexState:closeFunc()
	local fs = self.fs
	fs:ret(0, 0)
	fs:leaveBlock()
	local f = fs.f
	f.code = f.code or {}
	f.lineInfo = f.lineInfo or {}
	f.k = f.k or {}
	f.p = f.p or {}
	f.locVars = f.locVars or {}
	f.upvalues = f.upvalues or {}
	self:assert(fs.bl == nil)
	self.fs = fs.prev
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:fieldSel(v)
	local fs = self.fs
	local key = ExpressionDescriptor:new()
	fs:exp2AnyRegUp(v)
	self:next()
	self:checkName(key)
	fs:indexed(v, key)
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:yIndex(v)
	self:next()
	self:expr(v)
	self.fs:exp2Val(v)
	self:checkNext(B_RBRACKET)
end

--- @param cc TableConstructorControl
--- @return void
function LexState:recField(cc)
	local fs = self.fs
	local reg = self.fs.freeReg
	local key = ExpressionDescriptor:new()
	local val = ExpressionDescriptor:new()
	if self.t.token == self.TK_NAME then
		fs:checkLimit(cc.listElementCount, self.MAX_INT, 'items in a constructor')
		self:checkName(key)
	else
		self:yIndex(key)
	end
	cc.listElementCount = cc.listElementCount + 1
	self:checkNext(B_EQUALS)
	local rkKey = fs:exp2RK(key)
	self:expr(val)
	fs:codeABC(self.OP_SETTABLE, cc.tableExpression.expValue.opcodeInfo, rkKey, fs:exp2RK(val))
	fs.freeReg = reg
end

--- @param cc TableConstructorControl
--- @return void
function LexState:listField(cc)
	self:expr(cc.valueExpression)
	self.fs:checkLimit(cc.recordElementCount, self.MAX_INT, 'items in a constructor')
	cc.recordElementCount = cc.recordElementCount + 1
	cc.pendingStoreCount = cc.pendingStoreCount + 1
end

--- @param t ExpressionDescriptor
--- @return void
function LexState:constructor(t)
	local fs = self.fs
	local line = self.linenumber
	local pc = fs:codeABC(self.OP_NEWTABLE, 0, 0, 0)
	local cc = TableConstructorControl:new()
	cc.recordElementCount = 0
	cc.listElementCount = 0
	cc.pendingStoreCount = 0
	cc.tableExpression = t
	t:initialize(self.VRELOCABLE, pc)
	cc.valueExpression:initialize(self.VVOID, 0)
	fs:exp2NextReg(t)
	self:checkNext(B_LCURLY)
	repeat
		self:assert(cc.valueExpression.expressionType == self.VVOID or cc.pendingStoreCount > 0)
		local token = self.t.token
		if token == B_RCURLY then
			break
		end
		fs:closeListField(cc)
		if token == self.TK_NAME then
			self:lookAhead()
			if self.lookahead.token ~= B_EQUALS then
				self:listField(cc)
			else
				self:recField(cc)
			end
		elseif token == B_LBRACKET then
			self:recField(cc)
		else
			self:listField(cc)
		end
	until not (self:testNext(B_COMMA) or self:testNext(B_SEMICOLON))
	self:checkMatch(B_RCURLY, B_LCURLY, line)
	fs:lastListField(cc)
	local i = InstructionPointer:new(fs.f.code, pc)
	self:setArgB(i, self:encodeIntToFB(cc.recordElementCount))
	self:setArgC(i, self:encodeIntToFB(cc.listElementCount))
end

--- @param x number
--- @return number
function LexState:encodeIntToFB(x)
	local e = 0
	while x >= 16 do
		x = (x + 1) >> 1
		e = e + 1
	end
	if x < 8 then
		return x
	end
	return ((e + 1) << 3) | (x - 8)
end

--- @return void
function LexState:paramList()
	local fs = self.fs
	local f = fs.f
	local nParams = 0
	f.isVararg = 0
	if self.t.token ~= B_RPAREN then
		repeat
			local token = self.t.token
			if token == self.TK_NAME then
				self:newLocalVar(self:strCheckName())
				nParams = nParams + 1
			elseif token == self.TK_DOTS then
				self:next()
				f.isVararg = 1
			else
				self:syntaxError('<name> or ' .. self:LUA_QL('...') .. ' expected')
			end
		until not (f.isVararg == 0 and self:testNext(B_COMMA))
	end
	self:adjustLocalVars(nParams)
	f.numParams = fs.nActVar
	fs:reserveRegs(fs.nActVar)
end

--- @param e ExpressionDescriptor
--- @param needSelf boolean
--- @param line number
--- @return void
function LexState:base(e, needSelf, line)
	local newFs = FunctionState:new()
	local bl = BlockControl:new()
	newFs.f = self:addPrototype()
	newFs.f.lineDefined = line
	self:openFunc(newFs, bl)
	self:checkNext(B_LPAREN)
	if needSelf then
		self:newLocalVarLiteral('self')
		self:adjustLocalVars(1)
	end
	self:paramList()
	self:checkNext(B_RPAREN)
	self:statList()
	newFs.f.lastLineDefined = self.linenumber
	self:checkMatch(self.TK_END, self.TK_FUNCTION, line)
	self:codeClosure(e)
	self:closeFunc()
end

--- @param v ExpressionDescriptor
--- @return number
function LexState:expressionList(v)
	local n = 1
	local fs = self.fs
	self:expr(v)
	while self:testNext(B_COMMA) do
		fs:exp2NextReg(v)
		self:expr(v)
		n = n + 1
	end
	return n
end

--- @param f ExpressionDescriptor
--- @param line number
--- @return void
function LexState:funcArgs(f, line)
	local fs = self.fs
	--- @type ExpressionDescriptor
	local args = ExpressionDescriptor:new()
	do
		local token = self.t.token
		if token == B_LPAREN then
			self:next()
			if self.t.token == B_RPAREN then
				args.expressionType = self.VVOID
			else
				self:expressionList(args)
				fs:setMultipleRet(args)
			end
			self:checkMatch(B_RPAREN, B_LPAREN, line)
		elseif token == B_LCURLY then
			self:constructor(args)
		elseif token == self.TK_STRING then
			self:codeString(args, self.t.semanticInfo.stringValue)
			self:next()
		else
			self:syntaxError('function arguments expected')
		end
	end
	self:assert(f.expressionType == self.VNONRELOC)
	--- @type number
	local base = f.expValue.opcodeInfo
	--- @type number
	local nParams
	if self:hasMultipleRet(args.expressionType) then
		nParams = self.LUA_MULTRET
	else
		if args.expressionType ~= self.VVOID then
			fs:exp2NextReg(args)
		end
		nParams = fs.freeReg - (base + 1)
	end
	f:initialize(self.VCALL, fs:codeABC(self.OP_CALL, base, nParams + 1, 2))
	fs:fixLine(line)
	fs.freeReg = base + 1
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:primaryExp(v)
	local token = self.t.token
	if token == B_LPAREN then
		local line = self.linenumber
		self:next()
		self:expr(v)
		self:checkMatch(B_RPAREN, B_LPAREN, line)
		self.fs:disChargeVars(v)
	elseif token == self.TK_NAME then
		self:singleVar(v)
	else
		self:syntaxError('unexpected symbol ' .. token ..
				' (' .. string.char(token) .. ')')
	end
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:suffixedExp(v)
	local line = self.linenumber
	local fs = self.fs
	self:primaryExp(v)
	while true do
		local token = self.t.token
		if token == B_DOT then
			self:fieldSel(v)
		elseif token == B_LBRACKET then
			local key = ExpressionDescriptor:new()
			fs:exp2AnyRegUp(v)
			self:yIndex(key)
			fs:indexed(v, key)
		elseif token == B_COLON then
			local key = ExpressionDescriptor:new()
			self:next();
			self:checkName(key)
			fs:processSelf(v, key)
			self:funcArgs(v, line)
		elseif token == B_LPAREN or token == self.TK_STRING or
				token == B_LCURLY then
			fs:exp2NextReg(v)
			self:funcArgs(v, line)
		else
			return
		end
	end
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:simpleExp(v)
	local token = self.t.token
	if token == self.TK_NUMBER then
		v:initialize(self.VKNUM, 0)
		v.expValue:setNumericValue(self.t.semanticInfo.numericValue)
	elseif token == self.TK_STRING then
		self:codeString(v, self.t.semanticInfo.stringValue)
	elseif token == self.TK_NIL then
		v:initialize(self.VNIL, 0)
	elseif token == self.TK_TRUE then
		v:initialize(self.VTRUE, 0)
	elseif token == self.TK_FALSE then
		v:initialize(self.VFALSE, 0)
	elseif token == self.TK_DOTS then
		local fs = self.fs
		self:checkCondition(fs.f.isVararg ~= 0, 'cannot use ' ..
				self:LUA_QL('...') .. ' outside a vararg function')
		v:initialize(self.VVARARG, fs:codeABC(self.OP_VARARG, 0, 1, 0))
	elseif token == B_LCURLY then
		self:constructor(v)
		return
	elseif token == self.TK_FUNCTION then
		self:next()
		self:base(v, false, self.linenumber)
		return
	else
		self:suffixedExp(v)
		return
	end
	self:next()
end

--- @param op number
--- @return number
function LexState:getUnOpr(op)
	if op == self.TK_NOT then
		return self.OPR_NOT
	elseif op == B_MINUS then
		return self.OPR_MINUS
	elseif op == B_HASH then
		return self.OPR_LEN
	elseif op == B_TILDE then
		return self.OPR_BNOT
	else
		return self.OPR_NOUNOPR
	end
end

--- @param op number
--- @return number
function LexState:getBinOpr(op)
	if op == B_PLUS then
		return self.OPR_ADD
	elseif op == B_MINUS then
		return self.OPR_SUB
	elseif op == B_STAR then
		return self.OPR_MUL
	elseif op == B_SLASH then
		return self.OPR_DIV
	elseif op == B_PERCENT then
		return self.OPR_MOD
	elseif op == B_CARET then
		return self.OPR_POW
	elseif op == self.TK_CONCAT then
		return self.OPR_CONCAT
	elseif op == self.TK_NE then
		return self.OPR_NE
	elseif op == self.TK_EQ then
		return self.OPR_EQ
	elseif op == B_LESS_THAN then
		return self.OPR_LT
	elseif op == self.TK_LE then
		return self.OPR_LE
	elseif op == B_GREATER_THAN then
		return self.OPR_GT
	elseif op == self.TK_GE then
		return self.OPR_GE
	elseif op == self.TK_AND then
		return self.OPR_AND
	elseif op == self.TK_OR then
		return self.OPR_OR
	elseif op == self.TK_IDIV then
		return self.OPR_IDIV
	elseif op == B_AMPERSAND then
		return self.OPR_BAND
	elseif op == B_PIPE then
		return self.OPR_BOR
	elseif op == B_TILDE then
		return self.OPR_BXOR
	elseif op == self.TK_SHL then
		return self.OPR_SHL
	elseif op == self.TK_SHR then
		return self.OPR_SHR
	else
		return self.OPR_NOBINOPR
	end
end

--- @param v ExpressionDescriptor
--- @param limit number
--- @return number
function LexState:subExpr(v, limit)
	self:enterLevel()
	local uop = self:getUnOpr(self.t.token)
	if uop ~= self.OPR_NOUNOPR then
		local line = self.linenumber
		self:next()
		self:subExpr(v, self.UNARY_PRIORITY)
		self.fs:preFix(uop, v, line)
	else
		self:simpleExp(v)
	end
	local op = self:getBinOpr(self.t.token)
	local priority = self.priority
	while op ~= self.OPR_NOBINOPR and priority[op + 1].left > limit do
		local v2 = ExpressionDescriptor:new()
		local line = self.linenumber
		self:next()
		self.fs:inFix(op, v)
		local nextOp = self:subExpr(v2, priority[op + 1].right)
		self.fs:posFix(op, v, v2, line)
		op = nextOp
	end
	self:leaveLevel()
	return op
end

--- @param v ExpressionDescriptor
--- @return void
function LexState:expr(v)
	self:subExpr(v, 0)
end

--- @param withUntil boolean
--- @return boolean
function LexState:blockFollow(withUntil)
	local token = self.t.token
	if token == self.TK_ELSE or token == self.TK_ELSEIF or
			token == self.TK_END or token == self.TK_EOS then
		return true
	elseif token == self.TK_UNTIL then
		return withUntil
	else
		return false
	end
end

--- @return void
function LexState:block()
	local fs = self.fs
	fs:enterBlock(BlockControl:new(), false)
	self:statList()
	fs:leaveBlock()
end

--- @param lh LeftHandAssignment
--- @param v ExpressionDescriptor
function LexState:checkConflict(lh, v)
	local fs = self.fs
	local extra = fs.freeReg
	local conflict = false
	while lh ~= nil do
		if lh.expression.expressionType ~= self.VINDEXED then
			goto continue
		end
		if lh.expression.expValue.valueTypeIndex == v.expressionType and lh.expression.expValue.tIdx == v.expValue.opcodeInfo then
			conflict = true
			lh.expression.expValue.valueTypeIndex = self.VLOCAL
			lh.expression.expValue.tIdx = extra
		end
		if v.expressionType == self.VLOCAL and lh.expression.expValue.rkIdx == v.expValue.opcodeInfo then
			conflict = true
			lh.expression.expValue.rkIdx = extra
		end
		:: continue ::
		lh = lh.previousAssignment
	end
	if not conflict then
		return
	end
	local op = v.expressionType == self.VLOCAL and self.OP_MOVE or self.OP_GETUPVAL
	fs:codeABC(op, extra, v.expValue.opcodeInfo, 0)
	fs:reserveRegs(1)
end

--- @param lh LeftHandAssignment
--- @param nVars number
--- @return void
function LexState:assignment(lh, nVars)
	--- @type ExpressionDescriptor
	local e = ExpressionDescriptor:new()
	local fs = self.fs
	self:checkCondition(self.VLOCAL <= lh.expression.expressionType and lh.expression.expressionType <= self.VINDEXED, 'syntax error')
	if self:testNext(B_COMMA) then
		local nv = LeftHandAssignment:new()
		nv.previousAssignment = lh
		self:suffixedExp(nv.expression)
		if nv.expression.expressionType ~= self.VINDEXED then
			self:checkConflict(lh, nv.expression)
		end
		self:assignment(nv, nVars + 1)
	else
		self:checkNext(B_EQUALS)
		local nExp = self:expressionList(e)
		if nExp ~= nVars then
			self:adjustAssign(nVars, nExp, e)
			if nExp > nVars then
				fs.freeReg = fs.freeReg - (nExp - nVars)
			end
		else
			fs:setOneRet(e)
			fs:storeVar(lh.expression, e)
			return
		end
	end
	e:initialize(self.VNONRELOC, fs.freeReg - 1)
	fs:storeVar(lh.expression, e)
end

--- @return number
function LexState:cond()
	local v = ExpressionDescriptor:new()
	self:expr(v)
	if v.expressionType == self.VNIL then
		v.expressionType = self.VFALSE
	end
	self.fs:goIfTrue(v)
	return v.falseJumpTarget.i
end

--- @param pc number
--- @return void
function LexState:gotoStat(pc)
	local line = self.linenumber
	--- @type LuaString
	local label
	if self:testNext(self.TK_GOTO) then
		label = self:strCheckName()
	else
		self:next()
		label = LuaString:valueOf('break')
	end
	local dyd = self.dyd
	--- @type LabelDescriptor[]
	local gt = dyd.gotoLabels or {}
	dyd.gotoLabels = gt
	local g = self:newLabelEntry(gt, dyd.gotoLabelCount, label, line, pc)
	dyd.gotoLabelCount = dyd.gotoLabelCount + 1
	self:findLabel(g)
end

--- @return void
function LexState:skipNoOpStat()
	while self.t.token == B_SEMICOLON or self.t.token == self.TK_DBCOLON do
		self:statement()
	end
end

--- @param label LuaString
--- @param line number
--- @return void
function LexState:labelStat(label, line)
	local dyd = self.dyd
	local fs = self.fs
	fs:checkRepeated(dyd.labels, dyd.labelCount, label)
	self:checkNext(self.TK_DBCOLON)
	--- @type LabelDescriptor[]
	local dydLabel = dyd.labels or {}
	dyd.labels = dydLabel
	local l = self:newLabelEntry(dydLabel, dyd.labelCount, label, line, fs.pc)
	dyd.labelCount = dyd.labelCount + 1
	self:skipNoOpStat()
	if self:blockFollow(false) then
		dyd.labels[l + 1].activeVariableCount = fs.bl.nActVar
	end
	self:findGotos(dyd.labels[l + 1])
end

--- @param line number
--- @return void
function LexState:whileStat(line)
	local fs = self.fs
	local bl = BlockControl:new()
	self:next()
	local whileInit = fs:getLabel()
	local condExit = self:cond()
	fs:enterBlock(bl, true)
	self:checkNext(self.TK_DO)
	self:block()
	fs:patchList(fs:jump(), whileInit)
	self:checkMatch(self.TK_END, self.TK_WHILE, line)
	fs:leaveBlock()
	fs:patchToHere(condExit)
end

--- @param line number
--- @return void
function LexState:repeatStat(line)
	local fs = self.fs
	local repeatInit = fs:getLabel()
	local bl1 = BlockControl:new()
	local bl2 = BlockControl:new()
	fs:enterBlock(bl1, true)
	fs:enterBlock(bl2, false)
	self:next()
	self:statList()
	self:checkMatch(self.TK_UNTIL, self.TK_REPEAT, line)
	local condExit = self:cond()
	if bl2.upvalue then
		fs:patchCloseUpvalues(condExit, bl2.nActVar)
	end
	fs:leaveBlock()
	fs:patchList(condExit, repeatInit)
	fs:leaveBlock()
end

--- @return void
function LexState:oneExp()
	local e = ExpressionDescriptor:new()
	self:expr(e)
	self.fs:exp2NextReg(e)
end

--- @param base number
--- @param line number
--- @param nVar number
--- @param isNum boolean
--- @return void
function LexState:forBody(base, line, nVar, isNum)
	--- @type BlockControl
	local bl = BlockControl:new()
	local fs = self.fs
	self:adjustLocalVars(3)
	self:checkNext(self.TK_DO)
	local prep = isNum and fs:codeAsBx(self.OP_FORPREP, base, self.NO_JUMP) or fs:jump()
	fs:enterBlock(bl, false)
	self:adjustLocalVars(nVar)
	fs:reserveRegs(nVar)
	self:block()
	fs:leaveBlock()
	fs:patchToHere(prep)
	--- @type number
	local endFor
	if isNum then
		endFor = fs:codeAsBx(self.OP_FORLOOP, base, self.NO_JUMP)
	else
		fs:codeABC(self.OP_TFORCALL, base, 0, nVar)
		fs:fixLine(line)
		endFor = fs:codeAsBx(self.OP_TFORLOOP, base + 2, self.NO_JUMP)
	end
	fs:patchList(endFor, prep + 1)
	fs:fixLine(line)
end

--- @param varName LuaString
--- @param line number
--- @return void
function LexState:forNum(varName, line)
	local fs = self.fs
	local base = fs.freeReg
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_INDEX)
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_LIMIT)
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_STEP)
	self:newLocalVar(varName)
	self:checkNext(B_EQUALS)
	self:oneExp()
	self:checkNext(B_COMMA)
	self:oneExp()
	if self:testNext(B_COMMA) then
		self:oneExp()
	else
		fs:codeABx(self.OP_LOADK, fs.freeReg, fs:numberK(LuaNumber:valueOf(1)))
		fs:reserveRegs(1)
	end
	self:forBody(base, line, 1, true)
end

--- @param indexName LuaString
--- @return void
function LexState:forList(indexName)
	local fs = self.fs
	--- @type ExpressionDescriptor
	local e = ExpressionDescriptor:new()
	local nVar = 4
	local base = fs.freeReg
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_GENERATOR)
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_STATE)
	self:newLocalVarLiteral(self.RESERVED_LOCAL_VAR_FOR_CONTROL)
	self:newLocalVar(indexName)
	if self:testNext(B_COMMA) then
		self:newLocalVar(self:strCheckName())
		nVar = nVar + 1
	end
	self:checkNext(self.TK_IN)
	local line = self.linenumber
	self:adjustAssign(3, self:expressionList(e), e)
	fs:checkStack(3)
	self:forBody(base, line, nVar - 3, false)
end

--- @param line number
--- @return void
function LexState:forStat(line)
	local fs = self.fs
	local bl = BlockControl:new()
	fs:enterBlock(bl, true)
	self:next()
	local varName = self:strCheckName()
	local token = self.t.token
	if token == B_EQUALS then
		self:forNum(varName, line)
	elseif token == B_COMMA or token == self.TK_IN then
		self:forList(varName)
	else
		self:syntaxError(self:LUA_QL('=') .. ' or ' ..
				self:LUA_QL('in') .. ' expected')
	end
	self:checkMatch(self.TK_END, self.TK_FOR, line)
	fs:leaveBlock()
end

--- @param escapeList IntPointer
--- @return void
function LexState:testThenBlock(escapeList)
	--- @type ExpressionDescriptor
	local v = ExpressionDescriptor:new()
	--- @type BlockControl
	local bl = BlockControl:new()
	self:next()
	self:expr(v)
	self:checkNext(self.TK_THEN)
	--- @type number
	local jf
	local fs = self.fs
	if self.t.token == self.TK_GOTO or self.t.token == self.TK_BREAK then
		fs:goIfFalse(v)
		fs:enterBlock(bl, false)
		self:gotoStat(v.trueJumpTarget.i)
		self:skipNoOpStat()
		if self:blockFollow(false) then
			fs:leaveBlock()
			return
		else
			jf = fs:jump()
		end
	else
		fs:goIfTrue(v)
		fs:enterBlock(bl, false)
		jf = v.falseJumpTarget.i
	end
	self:statList()
	fs:leaveBlock()
	if self.t.token == self.TK_ELSE or self.t.token == self.TK_ELSEIF then
		fs:concat(escapeList, fs:jump())
	end
	fs:patchToHere(jf)
end

--- @param line number
--- @return void
function LexState:ifStat(line)
	--- @type IntPointer
	local escapeList = IntPointer:new(self.NO_JUMP)
	repeat
		self:testThenBlock(escapeList)
	until self.t.token ~= self.TK_ELSEIF
	if self:testNext(self.TK_ELSE) then
		self:block()
	end
	self:checkMatch(self.TK_END, self.TK_IF, line)
	self.fs:patchToHere(escapeList.i)
end

--- @return void
function LexState:localFunction()
	local fs = self.fs
	self:newLocalVar(self:strCheckName())
	self:adjustLocalVars(1)
	self:base(ExpressionDescriptor:new(), false, self.linenumber)
	fs:getLocVar(fs.nActVar - 1).startPc = fs.pc
end

--- @return void
function LexState:localStat()
	local nVars = 0
	--- @type number
	local nExp
	--- @type ExpressionDescriptor
	local e = ExpressionDescriptor:new()
	repeat
		self:newLocalVar(self:strCheckName())
		nVars = nVars + 1
	until not self:testNext(B_COMMA)
	if self:testNext(B_EQUALS) then
		nExp = self:expressionList(e)
	else
		e.expressionType = self.VVOID
		nExp = 0
	end
	self:adjustAssign(nVars, nExp, e)
	self:adjustLocalVars(nVars)
end

--- @param v ExpressionDescriptor
--- @return boolean
function LexState:functionName(v)
	local isMethod = false
	self:singleVar(v)
	while self.t.token == B_DOT do
		self:fieldSel(v)
	end
	if self.t.token == B_COLON then
		isMethod = true
		self:fieldSel(v)
	end
	return isMethod
end

--- @param line number
--- @return void
function LexState:functionStat(line)
	local v = ExpressionDescriptor:new()
	local b = ExpressionDescriptor:new()
	self:next()
	self:base(b, self:functionName(v), line)
	local fs = self.fs
	fs:storeVar(v, b)
	fs:fixLine(line)
end

--- @return void
function LexState:exprStat()
	--- @type LeftHandAssignment
	local v = LeftHandAssignment:new()
	self:suffixedExp(v.expression)
	if self.t.token == B_EQUALS or self.t.token == B_COMMA then
		v.previousAssignment = nil
		self:assignment(v, 1)
	else
		self:checkCondition(v.expression.expressionType == self.VCALL, 'syntax error')
		self:setArgC(self.fs:getCodePtr(v.expression), 1)
	end
end

--- @return void
function LexState:returnStat()
	local fs = self.fs
	--- @type ExpressionDescriptor
	local e = ExpressionDescriptor:new()
	--- @type number
	local first, nRet
	if self:blockFollow(true) or self.t.token == B_SEMICOLON then
		first = 0
		nRet = 0
	else
		nRet = self:expressionList(e)
		if self:hasMultipleRet(e.expressionType) then
			fs:setMultipleRet(e)
			if e.expressionType == self.VCALL and nRet == 1 then
				self:setOpCode(fs:getCodePtr(e), self.OP_TAILCALL)
				self:assert(self:getArgA(fs:getCode(e)) == fs.nActVar)
			end
			first = fs.nActVar
			nRet = self.LUA_MULTRET
		else
			if nRet == 1 then
				first = fs:exp2AnyReg(e)
			else
				fs:exp2NextReg(e)
				first = fs.nActVar
				self:assert(nRet == (fs.freeReg - first))
			end
		end
	end
	fs:ret(first, nRet)
	self:testNext(B_SEMICOLON)
end

--- @return void
function LexState:statement()
	local line = self.linenumber
	local fs = self.fs
	self:enterLevel()
	local token = self.t.token
	if token == B_SEMICOLON then
		self:next()
	elseif token == self.TK_IF then
		self:ifStat(line)
	elseif token == self.TK_WHILE then
		self:whileStat(line)
	elseif token == self.TK_DO then
		self:next()
		self:block()
		self:checkMatch(self.TK_END, self.TK_DO, line)
	elseif token == self.TK_FOR then
		self:forStat(line)
	elseif token == self.TK_REPEAT then
		self:repeatStat(line)
	elseif token == self.TK_FUNCTION then
		self:functionStat(line)
	elseif token == self.TK_LOCAL then
		self:next()
		if self:testNext(self.TK_FUNCTION) then
			self:localFunction()
		else
			self:localStat()
		end
	elseif token == self.TK_DBCOLON then
		self:next()
		self:labelStat(self:strCheckName(), line)
	elseif token == self.TK_RETURN then
		self:next()
		self:returnStat()
	elseif token == self.TK_BREAK or token == self.TK_GOTO then
		self:gotoStat(fs:jump())
	else
		self:exprStat()
	end
	self:assert(fs.f.maxStackSize >= fs.freeReg and fs.freeReg >= fs.nActVar)
	fs.freeReg = fs.nActVar
	self:leaveLevel()
end

--- @return void
function LexState:statList()
	while not self:blockFollow(true) do
		if self.t.token == self.TK_RETURN then
			self:statement()
			return
		end
		self:statement()
	end
end

--- @param funcState FunctionState
--- @return void
function LexState:mainFunc(funcState)
	self:openFunc(funcState, BlockControl:new())
	local fs = self.fs
	fs.f.isVararg = 1
	local v = ExpressionDescriptor:new()
	v:initialize(self.VLOCAL, 0)
	fs:newUpvalue(self.envName, v)
	self:next()
	self:statList()
	self:check(self.TK_EOS)
	self:closeFunc()
end

return LexState